package pCloud.service;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Random;

import com.sun.xacml.ctx.RequestCtx;
import com.sun.xacml.ctx.ResponseCtx;
import com.sun.xacml.ctx.Result;

import pCloud.acesscontrol.PolicyHelper;
import pCloud.acesscontrol.RequestHelper;
import pCloud.config.PCloudConfig;
import pCloud.crypto.kpabe.ACInstanceMessage;
import pCloud.crypto.kpabe.CipherMessage;
import pCloud.crypto.kpabe.KPAbe;
import pCloud.crypto.kpabe.TransformedCipherMessage;
import pCloud.crypto.kpabe.UKMessage;
import pCloud.transport.Communicator;
import pCloud.transport.TransportHandle;

/**
 * @author ttadinh Helper containing crypto functions for the cloud
 */
public class CloudCryptoHandle {

	KPAbe crypto;
	CloudServerProtocol com;
	HashMap<Integer, CloudSlidingWindowHandle> swMap;
	HashMap<Integer, Integer> alphaMap, betaMap;
	long throughputTimer = 0;
	long throughputCount = 0;
	public CloudCryptoHandle(CloudServerProtocol com) {
		crypto = new KPAbe();
		this.com = com;
		this.swMap = new HashMap<Integer, CloudSlidingWindowHandle>();
		this.alphaMap = new HashMap<Integer, Integer>();
		this.betaMap = new HashMap<Integer, Integer>();
	}

	// forward it along
	public void handleUserKeyMessage(UKMessage ukm) throws Exception {
		int ui = ukm.getUserId();
		this.alphaMap.put(ui, ukm.getAlpha());
		this.betaMap.put(ui, ukm.getBeta());
		 this.com.sendToUser(ui, ukm);
	}

	public void handleInitMessage(ACInstanceMessage acim) throws Exception {
		int uid = acim.getPolicyId();
		long start = System.currentTimeMillis();
		crypto.initACInstance(acim.getPubK(), acim.getStreamId(),
				acim.getPrvK(), uid);
		long end = System.currentTimeMillis();
		System.out.println("cloud setup: " + (end - start));
		if (acim.isSlidingWindowPolicy()) {
			CloudSlidingWindowHandle cswh = new CloudSlidingWindowHandle(
					crypto, acim.getBeta(), acim.getStreamId(), uid);
			swMap.put(uid, cswh);

			String fileName = "policy_" + acim.getStreamId() + "_" + uid;

			PolicyHelper.createSlidingWindowPolicy(acim.getStreamId(), uid,
					acim.getKeyorAlphaValue(), fileName);

			this.com.pdp.loadPolicy(fileName);

		} else {
			String fileName = "policy_" + acim.getStreamId() + "_" + uid;

			PolicyHelper.createSimplePolicy(acim.getStreamId(), uid,
					acim.getKeyorAlphaValue(), fileName);

			this.com.pdp.loadPolicy(fileName);

		}
	}

	/**
	 * We first construct the XACML request from the cph, then use PEP/PDP to
	 * see what policies fit
	 */
	public void handleCipherMessage(CipherMessage cph) throws Exception {		
		RequestCtx request = RequestHelper.createSimpleRequest(
				cph.getStreamId(), cph.getIndex());
		int[] policyIds = this.getMatchingPolicyIds(this.com.pdp
				.evaluate(request));

		if (policyIds == null)
			return;

		if (!cph.isSlidingWindow())
			for (int i = 0; i < policyIds.length; i++)
				this.transfromAndSendToUserSimple(cph.getStreamId(),
						policyIds[i], cph.getCipher(), cph.getEp(),
						cph.getIndex());
		else {
			for (int i = 0; i < policyIds.length; i++)
				this.transfromAndSendToUserSlidingWindow(cph, policyIds[i]);
		}
		
	}

	private int[] getMatchingPolicyIds(ResponseCtx response) {
		Iterator it = response.getResults().iterator();
		Result result;
		ArrayList<String> matchingPolicies;
		while (it.hasNext()) {
			result = (Result) it.next();
			if (result.getDecision() == Result.DECISION_PERMIT
					|| result.getDecision() == Result.DECISION_INDETERMINATE) {
				matchingPolicies = result.getMatchingPolicyIds();
				if (matchingPolicies.size() == 0)
					return null;
				int[] ret = new int[matchingPolicies.size()];
				for (int i = 0; i < ret.length; i++)
					ret[i] = new Integer(matchingPolicies.get(i)).intValue();
				return ret;
			} else
				return null;
		}
		return null;
	}

	private synchronized void transfromAndSendToUserSimple(int streamId, int userId,
			byte[] cph, byte[] ep, int idx) throws Exception {		
		
		long start = System.currentTimeMillis();
		byte[] tcph = crypto.transform_simple(cph, streamId, userId);
		long end = System.currentTimeMillis();
		System.out.println("cloud processing time: " + (end - start));
		TransformedCipherMessage cpm = new TransformedCipherMessage(streamId,
				tcph, ep, false, 0, idx);
		if (this.throughputCount==0)
			this.throughputTimer = System.currentTimeMillis();
		this.throughputCount++;
		long tmpTime = System.currentTimeMillis();
		if (tmpTime - this.throughputTimer>1000){
			System.out.println("cloud throughput = "+this.throughputCount);
			this.throughputCount=0;
			this.throughputTimer = System.currentTimeMillis();
		}
		this.com.sendToUser(userId, cpm);
	}

	private synchronized void transfromAndSendToUserSlidingWindow(CipherMessage cph, int uid)
			throws Exception {		
		if (cph.getIndex() < this.alphaMap.get(uid))
			System.err.println("Skip transformation");
		else {
			long start = System.currentTimeMillis();
			byte[] tcph = crypto.transform_simple(cph.getCipher(),
					cph.getStreamId(), uid);
			long end = System.currentTimeMillis();
			System.out.println("cloud processing time: " + (end - start));
			TransformedCipherMessage cpm = this.swMap.get(uid).compute(tcph,
					cph.getEp(), cph.getIndex());
			if (this.throughputCount==0)
				this.throughputTimer = System.currentTimeMillis();
			this.throughputCount++;
			long tmpTime = System.currentTimeMillis();
			if (tmpTime - this.throughputTimer > 1000) {
				System.out
						.println("cloud throughput = " + this.throughputCount);
				this.throughputCount = 0;
				this.throughputTimer = System.currentTimeMillis();
			}
			if (cpm != null) {				
				this.com.sendToUser(uid, cpm);
			}
		}
	}
}
